package awt.robot;

import static java.lang.Thread.currentThread;

import java.awt.Robot;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import concurrency.Condition;
import concurrency.LockSupport;

/**
 * This class aims to synchronise tests execution and application under test. Main idea is to wait for application idleness after each simplest robot action.
 * Look at {@link Robot#waitForIdle()} usage.
 * <p>
 * Application idleness is composite decision of several policies. There are few policies used by default. If it is not reasonable to generalise some conditions
 * to application level idle policy consider using {@link Sync#waitUntil(Condition)} and {@link Sync#waitUntil(long, Condition)} methods.
 * 
 * @author Mykhaylo Adamovych
 */
public class Sync {
    private static class CompositeIdlePolicy implements IdlePolicy {
        private final List<IdlePolicy> policies = new LinkedList<IdlePolicy>();

        public CompositeIdlePolicy() {
            addPolicy(new AwtIdlePolicy());
            addPolicy(new SwingIdlePolicy());
            addPolicy(new CoreIdlePolicy());
        }

        public void addPolicy(IdlePolicy policy) {
            policies.add(policy);
        }

        @Override
        public boolean isIdle() {
            for (IdlePolicy policy : policies)
                if (!policy.isIdle())
                    return false;
            return true;
        }

        public void removePolicy(IdlePolicy policy) {
            policies.remove(policy);
        }
    }

    static final CompositeIdlePolicy compositeIdlePolicy = new CompositeIdlePolicy();

    public static void addIdlePolicy(IdlePolicy policy) {
        compositeIdlePolicy.addPolicy(policy);
    }

    public static void removeIdlePolicy(IdlePolicy policy) {
        compositeIdlePolicy.removePolicy(policy);
    }

    /**
     * Wait for general application idleness. There are really few critical places where this should be used.
     */
    public static void waitForIdle() {
        waitUntil(new Condition() {
            @Override
            public boolean isSatisfied() {
                return true;
            }
        });
    }

    /**
     * Waits for both general application idleness based on composite idle policy and custom test-local condition.
     * 
     * @param condition
     * @see #waitUntil(long, Condition)
     * @author Mykhaylo Adamovych
     */
    public static void waitUntil(final Condition condition) {
        waitUntil(TimeUnit.MINUTES.toMillis(20), condition);
    }

    /**
     * Waits for both general application idleness based on composite idle policy and custom test-local condition.
     * 
     * @param checkIntervalMs
     * @param timeoutMs
     * @param condition
     * @see LockSupport#parkUntil(Condition condition)
     * @author Mykhaylo Adamovych
     */
    public static void waitUntil(long timeoutMs, final Condition condition) {
        int priorityBackup = currentThread().getPriority();
        final long staleThreashold = System.currentTimeMillis() + timeoutMs;
        LockSupport.parkUntil(new Condition() {
            @Override
            public boolean isSatisfied() {
                if (System.currentTimeMillis() > staleThreashold)
                    throw new AssertionError("Timed out.");
                boolean isSatisfied = compositeIdlePolicy.isIdle() && condition.isSatisfied();
                if (!isSatisfied)
                    currentThread().setPriority(Thread.NORM_PRIORITY - 1);
                return isSatisfied;
            }
        });
        currentThread().setPriority(priorityBackup);
    }
}
